<?php

/**
 * Erra_Persistance_SqlTable : IO class for fields save on a db table
 *
 * @package
 * @author Alexis Eric
 * @copyright Copyright (c) 2012 - Erra Prod
 * @version $1.0$
 * @access public
 */
class Erra_Persistance_SqlTable {

    public static $synchronizeStack = array();

    /**
     * Erra_Persistance_SqlRow::getSerializer() : Gets serialized used by class
     *
     * @return
     */
    public function getSerializer() {
        return $this->serializer;
    }

    /**
     * Erra_Persistance_SqlRow::__construct() : Initializes class with php serializer (default)
     */
    function __construct() {
        $this->serializer = new Erra_Serialization_SerializerJson();
    }

    /**
     * Erra_Persistance_SqlTable::getFieldFromDatabase() - Returns field from database
     *
     * @param mixed $field
     * @param mixed $WHERE_CLAUSE
     * @return
     */
    public function getFieldFromDatabase(&$field, $WHERE_CLAUSE = null) {
        $table = $field->getTableName();
        if (config_get('database.synchronize_schema') == 1) {
            self::synchronize($field);
        }
        $WHERE_CLAUSE = (!is_null($WHERE_CLAUSE)) ? ltrim(str_replace('WHERE ', '', ' AND ' . $WHERE_CLAUSE)) : '';

        if ($WHERE_CLAUSE == null) {
            if (is_numeric($field->getId()) && $field->getId() > -1) {
                $WHERE_CLAUSE .= ' AND id="' . $field->getId() . '"';
            } else {
                $WHERE_CLAUSE .= ' AND object_id="' . $field->getObjectId() . '"';
            }
        }
        // TODO : object_class = ... is really useful ?

        $r = Erra_Core_DatabasePdo::_SELECT('SELECT * FROM ' . $table . ' WHERE id=id ' . $WHERE_CLAUSE);

        if (!Erra_Core_DatabasePdo::rowCount()) {
            return $field;
        }

        // Simple Object
        if (Erra_Core_DatabasePdo::rowCount() && !count($field->getObjectDataArray())) {
            $r = $r->fetch();
            if (strlen($r['object_id'])) {
                $field->setObjectId($r['object_id']);
                $field->setName($r['object_id']);
            }
            // decrypt if necessary
            foreach ($r as $rowKey => $rowValue) {
                if (strstr($rowKey, '__crypt')) {
                    $r[$rowKey] = __decrypt($rowValue);
                }
            }
            Erra_Serialization_SerializerJson::unserializeField($field, $r);

            return $field;
        }
        // Field Array
        if (Erra_Core_DatabasePdo::rowCount()) {
            $result_array = array();
            while ($row = $r->fetch()) {
                $current_result = array();
                foreach ($row as $itemKey => $itemValue) {
                    if (is_numeric($itemKey) || $itemKey == 'object_class') // mots reservés
                        continue;
                    $current_result[$itemKey] = Erra_Serialization_SerializerJson::unserializeRaw($itemValue);
                    if (strstr($itemKey, '__crypt')) {
                        $current_result[$itemKey] = __decrypt($current_result[$itemKey]);
                    }
                }
                $result_array = $current_result;
            }
            $result_array = (count($result_array) == 1 && isset($result_array['rawData'])) ? $result_array['rawData'] : $result_array;

            Erra_Serialization_SerializerJson::unserializeField($field, $result_array);
        }
        return $field;
    }

    /**
     * Erra_Persistance_SqlTable::saveFieldToDatabase() - Saves field to database
     *
     * @param mixed $field
     * @return
     */
    public function saveFieldToDatabase($field) {
        // TODO : object_class = ... is really useful ?
        $fieldNeedle = $field->getObjectClass();
        $table = $field->getTableName();
        if (config_get('database.synchronize_schema') == 1) {
            self::synchronize($field);
        }
        // Verifier si le champ existe déja en base, auquel cas on fera un update
        $extraWhere = ' id=' . $field->getId();
        $r = Erra_Core_DatabasePdo::_SELECT('SELECT object_class FROM ' . $table . ' WHERE ' . $extraWhere);

        $insert = (Erra_Core_DatabasePdo::rowCount()) ? false : true;

        $this->saveComplexField($field, $table, $insert);
    }

    /**
     * Erra_Persistance_SqlTable::saveComplexField() - Save a complex field in database
     *
     * @param mixed $field
     * @param mixed $table
     * @param mixed $insert
     * @return
     */
    public function saveComplexField($field, $table, $insert = true) {
        // TODO : object_class = ... is really useful ?
        $c = $this->getDBArrayFromField($field, $table);
        $extraWhere = (isset($c['id']) && is_numeric($c['id'])) ? 'WHERE id=' . $field->getId() : '';
        $this->saveToTable($c, $table, $insert, ' ' . $extraWhere);
    }

    /**
     * Erra_Persistance_SqlTable::getDBObjectFromField() - Returns database object cursor from field
     *
     * @param mixed $field
     * @param mixed $table
     * @return
     */
    public function getDBArrayFromField($field, $table) {
        $c = array();
        $c['object_class'] = $field->getObjectClass();

        if ($field->getObjectId() != null) {
            $c['object_id'] = $field->getObjectId();
        }
        if ($field->idExists()) {
            $c['id'] = $field->getId();
        }
        if ($field->getSubFieldCount() > 0) {
            foreach ($field->getSubFields() as $aSubField) {
                if (stristr($aSubField->getDatabaseTypeString(), 'NONE') === FALSE) {
                    $subFieldName = $aSubField->getObjectId();
                    $c[$subFieldName] = is_null(Erra_Serialization_SerializerJson::serializeField($aSubField)) ? '' : Erra_Serialization_SerializerJson::serializeField($aSubField);
                    if (strstr($subFieldName, '__crypt')) {
                        $c[$subFieldName] = __crypt($c[$subFieldName]);
                    }
                }
            }
        }
        return $c;
    }

    /**
     * Erra_Persistance_SqlTable::saveToTable() - Save cursor into table
     *
     * @param mixed $c
     * @param mixed $insert
     * @param mixed $whereClause
     * @return
     */
    public function saveToTable($c, $table, $insert, $whereClause) {
        if ($insert) {
            $column_names = null;
            $column_values = null;
            $place_holders = array();
            foreach ($c as $column_name => $column_value) {
                if ($column_name != 'id') {
                    $column_names .= (is_null($column_names)) ? '' : ', ';
                    $column_names .= "$column_name";
                    $column_values .= (is_null($column_values)) ? '' : ', ';
                    $column_values .= '?';
                    $place_holders[] = $column_value;
                }
            }
            $request = 'INSERT INTO ' . $table . '(' . $column_names . ') VALUES(' . $column_values . ');';
            Erra_Core_DatabasePdo::EXECUTE($request, $place_holders);
        } else {


            $set_request = null;
            $place_holders = array();
            foreach ($c as $column_name => $column_value) {
                if ($column_name != 'id') {
                    $set_request .= (is_null($set_request)) ? '' : ', ';
                    $set_request .= "$column_name=?";
                    $place_holders[] = $column_value;
                }
            }
            $request = 'UPDATE ' . $table . ' SET ' . $set_request . ' ' . $whereClause;
            Erra_Core_DatabasePdo::EXECUTE($request, $place_holders);
        }
    }

    /**
     * Erra_Persistance_SqlTable::synchronize() - Synchronize table with field structure
     *
     * @param mixed $field
     * @param mixed $table
     * @return
     */
    public static function synchronize($field) {
        $table = $field->getTableName();
        // Don't  synchroize many times the same table on one application cycle
        if (in_array($table, self::$synchronizeStack) || !$field->isSynchronized()) {
            return false;
        }
        self::$synchronizeStack[] = $table;

        // Check first if the field is on read-only mode, and drop it's table if exists
        if ($field->getReadOnly() == true) {
            $r = Erra_Core_DatabasePdo::_SELECT('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=\'' . $table . '\'  AND COLUMN_NAME = "id" LIMIT 0, 1');
            if (Erra_Core_DatabasePdo::rowCount()) {
                try {
                    Erra_Core_DatabasePdo::EXECUTE('DROP TABLE ' . $table . '');
                    Erra_Core_Kernel::log('The table `' . $table . '` has been dropped with success', 'info');
                } catch (Exception $e) {
                    Erra_Core_Kernel::log('Can\'t drop the table `' . $table . '`');
                }
            }
            return true;
        }

        $model = $field;
        $langs = array_keys(Erra_Core_Locale::getAvailableLangs());
        $drop_db = array();
        $db = array();
        $db['id'] = 'INT(16)';
        $db['object_class'] = 'VARCHAR(64)';
        $db['object_id'] = 'VARCHAR(16)';

        //
        if ($model->getSubFieldCount()) {
            foreach ($model->getSubFields() as $aSubField) {
                $field_name = $aSubField->getObjectId();
                if (stristr($aSubField->getDatabaseTypeString(), 'NONE') === FALSE) {
                    $db[$field_name] = $aSubField->getDatabaseTypeString();
                } else {
                    $drop_db[$field_name] = true;
                }
            }
        }


// DEBUG_MODE_START DELETION
        // Create table if not exists
        Erra_Core_DatabasePdo::EXECUTE('CREATE TABLE IF NOT EXISTS ' . $table . ' ( object_class varchar(64) )');


        // Browse through all columns and check their integrity 
        $INFORMATION_SCHEMA = array();
        $r = Erra_Core_DatabasePdo::_SELECT('SELECT COLUMN_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = \'' . $table . '\' AND TABLE_SCHEMA= \'' . config_get('database.db_name') . '\'');
        if (Erra_Core_DatabasePdo::rowCount()) {
            while ($row = $r->fetch()) {
                $field_exists = false;
                $columnName = $row['COLUMN_NAME'];
                $columnType = strtoupper($row['COLUMN_TYPE']);
                $INFORMATION_SCHEMA[$columnName] = $columnType;
                if ($columnName == 'id' || $columnName == 'object_id' || $columnName == 'object_class')
                    continue;
                $columnBaseName = '';
                // Column name matches with an existing field
                if ($model->$columnName != null && !isset($drop_db[$columnName])) {
                    $field_exists = true;
                }


                if ($field_exists == false) {
                    // Drop field because it doesn't exists anymore

                    $result = Erra_Core_DatabasePdo::EXECUTE('ALTER TABLE ' . $table . ' DROP ' . $columnName);
                    if ($result) {
                        Erra_Core_Kernel::log('The field `' . $columnName . '` has been droped from the database because it doesn\'t exists anymore', 'info');
                    } else {
                        Erra_Core_Kernel::log('Can\'t drop column ' . $columnName . ' because it might not exists');
                    }
                } else {
                    // Update database type, if necessary
                    $columnBaseName = $columnName;
                    if ($columnBaseName != null && $columnType != $model->$columnBaseName->getDatabaseTypeString()) {
                        $result = Erra_Core_DatabasePdo::EXECUTE('ALTER TABLE ' . $table . ' CHANGE ' . $columnName . ' ' . $columnName . ' ' . $model->$columnBaseName->getDatabaseTypeString());
                    }
                    if (isset($result) && $result) {
                        Erra_Core_Kernel::log('Type of the field `' . $columnName . '` has been set from `' . $columnType . '` to `' . $model->$columnBaseName->getDatabaseTypeString() . '`', 'info');
                    } else if (isset($result)) {
                        Erra_Core_Kernel::log('Can\'t change type of field `' . $columnName . '` from `' . $columnType . '` to `' . $model->$columnBaseName->getDatabaseTypeString() . '`');
                    }
                }
            }
        }

        foreach ($db as $columnName => $columnType) {
            $type = (empty($columnType)) ? 'TEXT' : $columnType;
            if (!isset($INFORMATION_SCHEMA[$columnName]) && strlen($columnName)) {
                $request = 'ALTER TABLE ' . $table . ' ADD ' . $columnName . ' ' . $type;
                $result = Erra_Core_DatabasePdo::EXECUTE($request);
                if ($result) {
                    Erra_Core_Kernel::log('The field `' . $columnName . '` has been added to the table `' . $table . '` with success', 'info');
                } else {
                    Erra_Core_Kernel::log('Can\'t add the field `' . $columnName . '` of type `' . $type . '` to table`' . $table . '` !');
                }
            }
        }


        // Check if primary key and auto increment indice is setup
        $r = Erra_Core_DatabasePdo::_SELECT('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=\'' . $table . '\'  AND COLUMN_KEY = \'PRI\' AND TABLE_SCHEMA= \'' . config_get('database.db_name') . '\'');
        if (!Erra_Core_DatabasePdo::rowCount()) {
            try {
                Erra_Core_DatabasePdo::EXECUTE('ALTER TABLE ' . $table . ' ADD PRIMARY KEY(id)');
                Erra_Core_DatabasePdo::EXECUTE('ALTER TABLE ' . $table . ' CHANGE id id INT( 16 ) NOT NULL AUTO_INCREMENT');
                Erra_Core_Kernel::log('The table ' . $table . ' has been created with success', 'info');
            } catch (Exception $e) {
                Erra_Core_Kernel::log('Can\'t setup the table `' . $table . '` with correct primary key');
            }
        }
    }

}

?>